// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_terrain_region.h"
#include "mk_terrain_height_brush.h"
#include "mk_error.h"
#include "mk_math.h"
#include "mk_engine.h"
#include "mk_memory_block.h"
#include "mk_file_system.h"

namespace MK3DE
{        
	CTerrainRegion::CTerrainRegion()
	{
		memset(_neighboringRegion, 0, sizeof(CTerrainRegion*) * 4);
	}

	CTerrainRegion::~CTerrainRegion()
	{

	}

	void CTerrainRegion::__ClearTerrainData()
	{
		_terrainData.vecPosition.clear();
		_terrainData.vecNormal.clear();
		_terrainData.vecFaceNormal.clear();
		_terrainData.vecTexCoord.clear();
	}

	BOOL CTerrainRegion::CreateMesh(int regionX, int regionY)
	{
		__ClearTerrainData();

		for (DWORD j = 0; j < TERRAIN_REGION_HEIGHT_VERTEX; ++j)
		{
			for (DWORD i = 0; i < TERRAIN_REGION_WIDTH_VERTEX; ++i)
			{
				float x = (float)(i * TERRAIN_CELL_WIDTH);
				float z = (float)(j * TERRAIN_CELL_HEIGHT);
				float y = 0;
				_terrainData.vecPosition.push_back(CMesh::Position(x + (float)(TERRAIN_REGION_WIDTH_SIZE * regionX), 
					y, 
					z + (float)(TERRAIN_REGION_HEIGHT_SIZE * regionY)));
				_terrainData.vecNormal.push_back(CMesh::Normal(0, 1.0f, 0));
				_terrainData.vecTexCoord.push_back(CMesh::TextureCoord((float)i, (float)j));
			}
		}

		for (DWORD j = 0; j < TERRAIN_REGION_HEIGHT; j++)
		{
			for (DWORD i = 0; i < TERRAIN_REGION_WIDTH; i++)
			{
				_terrainData.vecFaceNormal.push_back(CMesh::Normal(0, 1.0f, 0));
				_terrainData.vecFaceNormal.push_back(CMesh::Normal(0, 1.0f, 0));
			}
		}

		return TRUE;
	}

	//void CTerrainRegion::RecalculateTextureCoord(DWORD texBlendMapSize)
	//{
	//	float mapSize = (float)texBlendMapSize;
	//	float tu0 = 1.0f / mapSize;
	//	float tv0 = 1.0f / mapSize;
	//	float dtx = (mapSize - 2.0f) / (float)(TERRAIN_REGION_WIDTH_VERTEX - 1) / mapSize;
	//	float dty = (mapSize - 2.0f) / (float)(TERRAIN_REGION_HEIGHT_VERTEX - 1) / mapSize;
	//	for (DWORD j = 0; j < TERRAIN_REGION_HEIGHT_VERTEX; ++j)
	//	{
	//		for (DWORD i = 0; i < TERRAIN_REGION_WIDTH_VERTEX; ++i)
	//		{
	//			_terrainData.vecTexCoord[j * TERRAIN_REGION_WIDTH_VERTEX + i].tu = tu0 + i * dtx;
	//			_terrainData.vecTexCoord[j * TERRAIN_REGION_WIDTH_VERTEX + i].tv = tv0 + j * dty;
	//		}
	//	}
	//}

	BOOL CTerrainRegion::LoadHeightMap(const char* fileName)
	{
		CMemoryBlock block;

		BOOL ret = GetEngine()->GetFileSystem()->ReadFile(fileName, &block);
		MK_PROCESS_ERROR(ret);

		MK_PROCESS_ERROR(block.GetSize() / sizeof(DWORD) == _terrainData.vecPosition.size());

		DWORD* buffer = (DWORD*)block.GetBuffer();
		for (int i = 0; i < (int)_terrainData.vecPosition.size(); ++i)
		{
			float height = *(float*)&buffer[i];
			_terrainData.vecPosition[i].y = height;
		}

		RecalculateFaceNormal();

		return TRUE;
Exit0:
		return FALSE;
	}

	BOOL CTerrainRegion::SaveHeightMap(const char* fileName)
	{
		FILE *file = fopen(fileName, "wb");
		if (!file)
			return FALSE;

		for (int i = 0; i < (int)_terrainData.vecPosition.size(); ++i)
		{
			DWORD height = *(DWORD*)&_terrainData.vecPosition[i].y;
			fwrite(&height, 1, sizeof(DWORD), file);
		}

		fclose(file);
		return TRUE;
	}

	void CTerrainRegion::AttachNeighboringRegion(CTerrainRegion* Up, CTerrainRegion* Right, CTerrainRegion* Down, CTerrainRegion* Left)
	{
		_neighboringRegion[0] = Up;
		_neighboringRegion[1] = Right;
		_neighboringRegion[2] = Down;
		_neighboringRegion[3] = Left;
	}

	CTerrainRegion* CTerrainRegion::GetNeighboringRegion(DWORD idx)
	{
		if (idx < 4)
			return _neighboringRegion[idx];

		return NULL;
	}

	void CTerrainRegion::RecalculateFaceNormal()
	{
		for (DWORD j = 0; j < TERRAIN_REGION_HEIGHT; j++)
		{
			for (DWORD i = 0; i < TERRAIN_REGION_WIDTH; i++)
			{
				D3DXVECTOR3 v1 = _terrainData.vecPosition[TERRAIN_REGION_WIDTH_VERTEX * j + i];
				D3DXVECTOR3 v2 = _terrainData.vecPosition[TERRAIN_REGION_WIDTH_VERTEX * j + i + 1];
				D3DXVECTOR3 v3 = _terrainData.vecPosition[TERRAIN_REGION_WIDTH_VERTEX * (j + 1) + i];
				D3DXVECTOR3 v4 = _terrainData.vecPosition[TERRAIN_REGION_WIDTH_VERTEX * (j + 1) + i + 1];

				D3DXVECTOR3* normal = &_terrainData.vecFaceNormal[2 * (TERRAIN_REGION_WIDTH * j + i)];

				D3DXVECTOR3 d1 = v3 - v1;
				D3DXVECTOR3 d2 = v2 - v1;
				D3DXVec3Cross(normal, &d1, &d2);
				D3DXVec3Normalize(normal, normal);

				d1 = v2 - v4;
				d2 = v3 - v4;
				normal++;
				D3DXVec3Cross(normal, &d1, &d2);
				D3DXVec3Normalize(normal, normal);
			}
		}
	}

	BOOL CTerrainRegion::__GetFaceNormal(CMesh::Normal& normal, int x, int y, DWORD idx)
	{
		CTerrainRegion* region = this;
		if (x < 0)
		{
			region = region->GetNeighboringRegion(REGION_IDX_LEFT);
			if (region)
			{
				x += TERRAIN_REGION_WIDTH;

				if (y < 0)
				{
					region = region->GetNeighboringRegion(REGION_IDX_DOWN);
					if (region)
						y += TERRAIN_REGION_HEIGHT;
				}
				else if (y > TERRAIN_REGION_HEIGHT - 1)
				{
					region = region->GetNeighboringRegion(REGION_IDX_UP);
					if (region)
						y -= TERRAIN_REGION_HEIGHT;
				}
			}
		}
		else if (x > TERRAIN_REGION_WIDTH - 1)
		{
			region = region->GetNeighboringRegion(REGION_IDX_RIGHT);
			if (region)
			{
				x -= TERRAIN_REGION_WIDTH;

				if (y < 0)
				{
					region = region->GetNeighboringRegion(REGION_IDX_DOWN);
					if (region)
						y += TERRAIN_REGION_HEIGHT;
				}
				else if (y > TERRAIN_REGION_HEIGHT - 1)
				{
					region = region->GetNeighboringRegion(REGION_IDX_UP);
					if (region)
						y -= TERRAIN_REGION_HEIGHT;
				}
			}
		}
		else if (y < 0)
		{
			region = region->GetNeighboringRegion(REGION_IDX_DOWN);
			if (region)
				y += TERRAIN_REGION_HEIGHT;
		}
		else if (y > TERRAIN_REGION_HEIGHT - 1)
		{
			region = region->GetNeighboringRegion(REGION_IDX_UP);
			if (region)
				y -= TERRAIN_REGION_HEIGHT;
		}

		if (region)
			return region->GetFaceNormal(normal, x, y, idx);

		return FALSE;
	}

	BOOL CTerrainRegion::GetFaceNormal(CMesh::Normal& normal, DWORD x, DWORD y, DWORD idx)
	{
		if (x < TERRAIN_REGION_WIDTH && y < TERRAIN_REGION_HEIGHT && idx < 2)
		{
			normal = _terrainData.vecFaceNormal[2 * (y * TERRAIN_REGION_WIDTH + x) + idx];
			return TRUE;
		}

		return FALSE;
	}

	void CTerrainRegion::RecalculateNormal()
	{
		RecalculateFaceNormal();
		for (DWORD y = 0; y < (DWORD)TERRAIN_REGION_HEIGHT_VERTEX; ++y)
		{
			for (DWORD x = 0; x < (DWORD)TERRAIN_REGION_WIDTH_VERTEX; ++x)
			{
				DWORD cnt = 0;
				CMesh::Normal normal(0, 0, 0);
				CMesh::Normal n(0, 0, 0);
				if (__GetFaceNormal(n, x - 1, y - 1, 1))
				{
					cnt++;
					normal += n;
				}

				if (__GetFaceNormal(n, x, y - 1, 0))
				{
					cnt++;
					normal += n;
				}

				if (__GetFaceNormal(n, x, y - 1, 1))
				{
					cnt++;
					normal += n;
				}

				if (__GetFaceNormal(n, x - 1, y, 0))
				{
					cnt++;
					normal += n;
				}

				if (__GetFaceNormal(n, x - 1, y, 1))
				{
					cnt++;
					normal += n;
				}

				if (__GetFaceNormal(n, x, y, 0))
				{
					cnt++;
					normal += n;
				}

				_terrainData.vecNormal[y * TERRAIN_REGION_WIDTH_VERTEX + x] = normal / (float)cnt;
			}
		}
	}

	BOOL CTerrainRegion::FillPositionBuffer(CMesh::Position* pPos, int vertexNum)
	{
		if (vertexNum != (int)_terrainData.vecPosition.size())
			return FALSE;

		memcpy(pPos, &_terrainData.vecPosition[0].x, sizeof(CMesh::Position) * vertexNum);

		return TRUE;
	}

	BOOL CTerrainRegion::FillNormalBuffer(CMesh::Normal* pNormal, int vertexNum)
	{
		if (vertexNum != (int)_terrainData.vecNormal.size())
			return FALSE;

		memcpy(pNormal, &_terrainData.vecNormal[0].x, sizeof(CMesh::Normal) * vertexNum);

		return TRUE;
	}

	BOOL CTerrainRegion::FillTextureCoordBuffer(CMesh::TextureCoord* pUV, int vertexNum)
	{
		if (vertexNum != (int)_terrainData.vecTexCoord.size())
			return FALSE;

		memcpy(pUV, &_terrainData.vecTexCoord[0].tu, sizeof(CMesh::TextureCoord) * vertexNum);

		return TRUE;

	}

	BOOL CTerrainRegion::FillFaceBuffer(CMesh::Face* pFace, int faceNum)
	{
		if (faceNum != TERRAIN_REGION_WIDTH * TERRAIN_REGION_HEIGHT * 2)
			return FALSE;

		memcpy(pFace, TERRAIN_INDEX_L0_0, faceNum * 3 * sizeof(DWORD));
		return TRUE;
	}

	BOOL CTerrainRegion::GetVertexPosition(D3DXVECTOR3& pos, DWORD x, DWORD y)
	{
		if (x < TERRAIN_REGION_WIDTH_VERTEX && y < TERRAIN_REGION_HEIGHT_VERTEX)
		{
			pos = _terrainData.vecPosition[y * TERRAIN_REGION_WIDTH_VERTEX + x];
			return TRUE;
		}

		return FALSE;
	}

	BOOL CTerrainRegion::SetVertexPosition(D3DXVECTOR3& pos, DWORD x, DWORD y)
	{
		if (x < TERRAIN_REGION_WIDTH_VERTEX && y < TERRAIN_REGION_HEIGHT_VERTEX)
		{
			_terrainData.vecPosition[y * TERRAIN_REGION_WIDTH_VERTEX + x] = pos;
			return TRUE;
		}

		return FALSE;
	}

	BOOL CTerrainRegion::CollideWithRay(D3DXVECTOR3& pos, float x, float y, float z, D3DXVECTOR3& dir)
	{
		for (DWORD j = 0; j < TERRAIN_REGION_HEIGHT; j++)
		{
			for (DWORD i = 0; i < TERRAIN_REGION_WIDTH; i++)
			{
				D3DXVECTOR3 v1 = _terrainData.vecPosition[TERRAIN_REGION_WIDTH_VERTEX * j + i];
				D3DXVECTOR3 v2 = _terrainData.vecPosition[TERRAIN_REGION_WIDTH_VERTEX * j + i + 1];
				D3DXVECTOR3 v3 = _terrainData.vecPosition[TERRAIN_REGION_WIDTH_VERTEX * (j + 1) + i];
				D3DXVECTOR3 v4 = _terrainData.vecPosition[TERRAIN_REGION_WIDTH_VERTEX * (j + 1) + i + 1];

				if (RayIntersectTriangle(pos, D3DXVECTOR3(x, y, z), dir, v1, v2, v3))
					return TRUE;

				if (RayIntersectTriangle(pos, D3DXVECTOR3(x, y, z), dir, v2, v3, v4))
					return TRUE;
			}
		}

		return FALSE;
	}
}

